package com.sbtr.common.calculate;

import com.alibaba.fastjson.JSON;
import com.sbtr.common.enums.CalculateEnum;
import com.sbtr.common.interfaces.Calculate;
import org.junit.Assert;
import org.junit.Test;

/**
 * @author maojianping
 * @time 2023-03-22-21:21
 * 数据标准化工具类
 *   1.最大值归一化
 *   2.最大最小归一化
 *   3.z-score 标准化
 *   4.中心化标准化
 * 数据预处理方法：主要将数据缩放到0和1之间
 */
public class DataStandardUtils {

    /**
     * 1.最大值归一化
     * a. 对于每个数据d，将其除以max，得到归一化后的数据。
     * @param data
     * @return
     */
    @Calculate(CalculateEnum.normalizeMaxValue)
    public static double[] normalizeMaxValue(double[] data) {
        // 初始化用最小值
        double max = Double.MIN_VALUE;
        for (double d : data) {
            if (d > max) {
                max = d;
            }
        }
        double[] norma = new double[data.length];
        for (int i = 0; i < data.length; i++) {
            norma[i] = data[i] / max;
        }
        return norma;
    }

    /**
     * 1.最大值归一化：测试案例
     */
    @Test
    public void testNormalizeMaxValue() {
        double[] data = {100, 200, 300, 400, 500};
//        double[] data = {10, 20, 30, 40, 50};
//        double[] data = {1.0, 2.0, 3.0, 4.0, 5.0};
        double[] expected = {0.2, 0.4, 0.6, 0.8, 1.0};
        double[] result = DataStandardUtils.normalizeMaxValue(data);
        System.err.println(JSON.toJSONString(result));
        Assert.assertArrayEquals(expected, result, 0.0001);
    }

    /**
     * 2. 最大最小值归一化
     * a.归一化处理公式：(arr[i] - min) / (max - min)
     * @param arr
     * @return
     */
    @Calculate(CalculateEnum.normalizeMinMax)
    public static double[] normalizeMinMax(double[] arr) {
        // 初始化最大最小值进行比较
        double max = Double.MIN_VALUE;
        double min = Double.MAX_VALUE;
        for (double d : arr) {
            if (d > max) {
                max = d;
            }
            if (d < min) {
                min = d;
            }
        }
        double[] normalizedArr = new double[arr.length];
        for (int i = 0; i < arr.length; i++) {
            normalizedArr[i] = (arr[i] - min) / (max - min);
        }
        return normalizedArr;
    }
    /**
     * 2. 最大最小值归一化：测试案例
     */
    @Test
    public void testNormalizeMinMax() {
        double[] arr = {1.0, 2.0, 3.0, 4.0, 5.0};
        double[] expected = {0.0, 0.25, 0.5, 0.75, 1.0}; // 待对比数据
        double[] result = DataStandardUtils.normalizeMinMax(arr);
        System.err.println(JSON.toJSONString(result)); // 标准化后数据
        Assert.assertArrayEquals(expected, result, 0.001); // 误差范围
    }

    /**
     * 3. z-score 标准化是基于正态分布的，该方法假设数据呈现标准正态分布
     * 注：z-score标准化不改变原始数据分布情况，即原始数据符合正态分布，转换后就符合，不符合的转换后也不符合正态分布的
     * @param arr
     * @return
     */
    @Calculate(CalculateEnum.zScoreNormalization)
    public static double[] zScoreNormalization(double[] arr) {
        double mean = 0.0;
        double stdDev = 0.0;
        int length = arr.length;
        for (double num : arr) {
            mean += num;
        }
        mean /= length;
        for (double num : arr) {
            stdDev += Math.pow(num - mean, 2); // 计算平均值的差的平方，并加到stdDev变量中。
        }
//        stdDev /= length; // 使用样本标准差，存在误差，大数据了试用
        stdDev /= (length - 1); // 计算数组的标准差,使用总体标准差
        stdDev = Math.sqrt(stdDev);// z-score标准化值
        double[] normalizedArr = new double[length];
        for (int i = 0; i < length; i++) {
            normalizedArr[i] = (arr[i] - mean) / stdDev;
        }
        return normalizedArr;
    }

    /**
     * 3. z-score 标准化：测试案例
     */
    @Test
    public void testZScoreNormalization() {
        double[] arr = {1.0, 2.0, 3.0, 4.0, 5.0};
        double[] expected = {-1.2649110640673518, -0.6324555320336759, 0.0, 0.6324555320336759, 1.2649110640673518};
        double[] result = DataStandardUtils.zScoreNormalization(arr);
        System.err.println(JSON.toJSONString(result));
        Assert.assertArrayEquals(expected, result, 0.0001); //  容错范围
    }


    /**
     * 3. 中心化标准化
     * 注：作用是将原始数据转化为均值为0，标准差为1的数据，使得数据更易于比较和分析。同时，中心化标准化可以消除数据中的量纲影响，使得不同单位的数据可以进行比较
     * @param data
     * @return
     */
    @Calculate(CalculateEnum.centralization)
    public static double[] centralization(double[] data) {
        double[] result = new double[data.length];
        double sum = 0;
        for (int i = 0; i < data.length; i++) {
            sum += data[i];
        }
        double avg = sum / data.length;
        double sum2 = 0;
        for (int i = 0; i < data.length; i++) {
            sum2 += Math.pow(data[i] - avg, 2);
        }
        //计算data数组的标准差
        double std = Math.sqrt(sum2 / data.length);
        for (int i = 0; i < data.length; i++) {
            result[i] = (data[i] - avg) / std; // 中心化标准化公式
        }
        return result;
    }

    @Test
    public void testCentralization() {
        double[] data = {1, 2, 3, 4, 5};
        double[] expected = {-1.41421356, -0.70710678, 0, 0.70710678, 1.41421356};
        double[] result = DataStandardUtils.centralization(data);
        System.err.println(JSON.toJSONString(result));
        Assert.assertArrayEquals(expected, result, 0.0001);
    }

}
